package com.jms.framework.beans.context;

import com.jms.framework.aware.ApplicationContextAwareInitializer;
import com.jms.framework.beans.BeanDefinition;
import com.jms.framework.beans.BeanDependency;
import com.jms.framework.beans.creator.BeanDefinitionCreator;
import com.jms.framework.beans.creator.SingletonBeanFactoryCreator;
import com.jms.framework.beans.factory.ObjectFactory;
import com.jms.framework.beans.scan.ComponentScanner;
import com.jms.framework.beans.exception.BeansException;
import com.jms.framework.context.ApplicationContext;
import com.jms.framework.exception.DataNotLegitimateException;
import com.jms.framework.exception.PropertyAlreadySetException;
import com.jms.framework.beans.utils.BeanInitializeUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 抽象Bean上下文类，实现了BeanContext接口
 */
public abstract class AbstractBeanContext implements BeanContext {

    // Bean名称列表
    private List<String> beanNames;

    // 存储Bean定义的ConcurrentHashMap
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitions;

    // 存储单例Bean对象的ConcurrentHashMap
    private ConcurrentHashMap<String, Object> singletonObjects;

    // 存储早期单例Bean对象的ConcurrentHashMap
    private ConcurrentHashMap<String, Object> earlySingletonObjects;

    // 存储单例Bean工厂的ConcurrentHashMap
    private ConcurrentHashMap<String, ObjectFactory<?>> singletonFactories;

    // 应用上下文对象
    private ApplicationContext applicationContext;

    private ComponentScanner componentScanner;

    /**
     * 默认创建单例Bean工厂的方法
     *
     * @param beanDefinition Bean定义
     * @return ObjectFactory对象
     * @throws BeansException 如果没有可用的SingletonBeanFactoryCreator则抛出异常
     * @throws DataNotLegitimateException 如果无法识别Bean定义类型则抛出异常
     */
    protected ObjectFactory<?> defaultCreateSingletonBeanFactory(BeanDefinition beanDefinition) {
        List<SingletonBeanFactoryCreator> singletonBeanFactoryCreators = componentScanner.getSingletonBeanFactoryCreators();
        if (singletonBeanFactoryCreators == null || singletonBeanFactoryCreators.isEmpty()) {
            throw new BeansException("SingletonBeanFactoryCreators is null or empty");
        }
        for (SingletonBeanFactoryCreator singletonBeanFactoryCreator : singletonBeanFactoryCreators) {
            ObjectFactory<?> objectFactory = singletonBeanFactoryCreator.create(beanDefinition, applicationContext);
            if (objectFactory != null) {
                return objectFactory;
            }
        }
        throw new DataNotLegitimateException("Unrecognized bean definition type: " + beanDefinition.getClass().getName());
    }

    /**
     * 默认创建Bean定义的方法
     *
     * @param clazz Bean类
     * @return BeanDefinition对象，如果无法创建则返回null
     * @throws BeansException 如果没有可用的BeanDefinitionCreator则抛出异常
     */
    protected BeanDefinition defaultCreateBeanDefinition(Class<?> clazz) {
        List<BeanDefinitionCreator> beanDefinitionCreators = componentScanner.getBeanDefinitionCreators();
        if (beanDefinitionCreators == null || beanDefinitionCreators.isEmpty()) {
            throw new BeansException("BeanDefinitionCreators is null or empty");
        }
        for (BeanDefinitionCreator beanDefinitionCreator : beanDefinitionCreators) {
            BeanDefinition beanDefinition = beanDefinitionCreator.create(clazz, applicationContext);
            if (beanDefinition != null) {
                return beanDefinition;
            }
        }
        return null;
    }

    /**
     * 创建单例Bean工厂的方法，由子类实现
     *
     * @param beanDefinition Bean定义
     * @return ObjectFactory对象
     */
    protected abstract ObjectFactory<?> createSingletonBeanFactory(BeanDefinition beanDefinition);

    /**
     * 创建Bean定义的方法，由子类实现
     *
     * @param clazz Bean类
     * @return BeanDefinition对象
     */
    protected abstract BeanDefinition createBeanDefinition(Class<?> clazz);

    /**
     * 设置应用上下文方法
     *
     * @param applicationContext 应用上下文对象
     * @throws PropertyAlreadySetException 如果应用上下文已设置，则抛出此异常
     */
    public void setApplicationContext(ApplicationContext applicationContext) {
        if (this.applicationContext != null) {
            throw new PropertyAlreadySetException(ApplicationContext.class, "applicationContext");
        }
        this.applicationContext = applicationContext;
    }

    /**
     * 根据Bean定义获取Bean对象
     *
     * @param beanDefinition Bean定义
     * @return Bean对象
     */
    private Object getBean(BeanDefinition beanDefinition) {
        if (beanDefinition.getScope().equals(BeanDefinition.SCOPE_SINGLETON)) {
            return getSingletonBean(beanDefinition.getBeanName());
        } else if (beanDefinition.getScope().equals(BeanDefinition.SCOPE_PROTOTYPE)) {
            return getPrototypeBean(beanDefinition);
        } else {
            return null;
        }
    }

    /**
     * 获取原型Bean对象
     *
     * @param beanDefinition Bean定义
     * @return 原型Bean对象
     */
    private Object getPrototypeBean(BeanDefinition beanDefinition) {
        try {
            return null;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 将Bean定义放入beanDefinitions中的方法
     *
     * @param beanDefinition Bean定义
     * @throws DataNotLegitimateException 如果Bean名称重复，则抛出此异常
     */
    private void putBeanDefinition(BeanDefinition beanDefinition) {
        if (beanDefinitions.containsKey(beanDefinition.getBeanName())) {
            throw new DataNotLegitimateException(String.format("the bean is not unique: %s", beanDefinition.getBeanName()));
        }
        beanDefinitions.put(beanDefinition.getBeanName(), beanDefinition);
        beanNames.add(beanDefinition.getBeanName());
    }

    /**
     * 获取单例Bean对象
     *
     * @param beanName Bean名称
     * @return 单例Bean对象
     */
    private Object getSingletonBean(String beanName) {
        try {
            if (singletonObjects.containsKey(beanName)) {
                return singletonObjects.get(beanName);
            }
            if (earlySingletonObjects.containsKey(beanName)) {
                return earlySingletonObjects.get(beanName);
            }
            if (singletonFactories.containsKey(beanName)) {
                Object object = singletonFactories.get(beanName).getObject();
                earlySingletonObjects.put(beanName, object);
                singletonFactories.remove(beanName);
                return object;
            }
            return null;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 根据Bean类获取Bean定义
     *
     * @param beanClass Bean类
     * @return Bean定义
     */
    private BeanDefinition getBeanDefinition(Class<?> beanClass) {
        try {
            BeanDefinition beanDefinition = null;
            for (BeanDefinition beanDefinition1 : beanDefinitions.values()) {
                Class<?> clazz = Class.forName(beanDefinition1.getBeanClassName());
                if (clazz.equals(beanClass) || beanClass.isAssignableFrom(clazz)) {
                    if (beanDefinition != null) {
                        throw new DataNotLegitimateException(String.format("the bean is not unique: %s, bean class: ", beanClass.getName()));
                    }
                    beanDefinition = beanDefinition1;
                }
            }
            return beanDefinition;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 创建单例Bean对象
     *
     * @param beanDefinition Bean定义
     * @return 单例Bean对象
     */
    private Object createSingletonBean(BeanDefinition beanDefinition) {
        try {
            ObjectFactory<?> objectFactory = createSingletonBeanFactory(beanDefinition);
            Object thisBean = null;
            singletonFactories.put(beanDefinition.getBeanName(), objectFactory);
            BeanDependency[] dependencies = beanDefinition.getDependencies();
            if (dependencies != null && dependencies.length > 0) {
                for (BeanDependency dependency : dependencies) {
                    String dependencyClassName = dependency.getClassName();
                    String dependencyBeanName = dependency.getBeanName();
                    BeanDefinition dependencyBeanDefinition = dependencyBeanName == null ? getBeanDefinition(Class.forName(dependencyClassName)) : beanDefinitions.get(dependencyBeanName);
                    if (dependencyBeanDefinition == null) {
                        throw new DataNotLegitimateException(String.format("the bean is not exist: %s", dependencyClassName));
                    }
                    if (dependencyBeanDefinition.getScope().equals(BeanDefinition.SCOPE_SINGLETON)) {
                        Object dependencyBean = getSingletonBean(dependencyBeanDefinition.getBeanName());
                        if (dependencyBean == null) {
                            dependencyBean = createSingletonBean(dependencyBeanDefinition);
                        }
                        if (thisBean == null) {
                            thisBean = getSingletonBean(beanDefinition.getBeanName());
                        }
                        if (dependency.getMethod() != null) {
                            dependency.getMethod().invoke(thisBean, dependencyBean);
                        }
                    }
                }
            }
            if (thisBean == null) {
                thisBean = getSingletonBean(beanDefinition.getBeanName());
            }

            if (beanDefinition.getInitMethodName() != null) {
                Method method = Objects.requireNonNull(thisBean).getClass().getMethod(beanDefinition.getInitMethodName());
                method.invoke(thisBean);
            }

            singletonObjects.put(beanDefinition.getBeanName(), Objects.requireNonNull(thisBean));
            earlySingletonObjects.remove(beanDefinition.getBeanName());
            return thisBean;
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 根据Bean名称获取Bean对象
     *
     * @param beanName Bean名称
     * @return Bean对象
     */
    @Override
    public Object getBean(String beanName) {
        try {
            BeanDefinition beanDefinition;
            if ((beanDefinition = beanDefinitions.get(beanName)) == null) {
                throw new DataNotLegitimateException(String.format("the bean named %s is not exist", beanName));
            }
            return getBean(beanDefinition);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 根据Bean类获取Bean对象
     *
     * @param beanClass Bean类
     * @param <T>       Bean类型
     * @return Bean对象
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getBean(Class<T> beanClass) {
        try {
            BeanDefinition beanDefinition = getBeanDefinition(beanClass);
            if (beanDefinition == null) {
                return null;
            }
            return (T) getBean(beanDefinition);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 获取所有Bean对象数组
     *
     * @return Bean对象数组
     */
    @Override
    public Object[] getBeans() {
        return singletonObjects.values().toArray();
    }

    /**
     * 初始化Bean上下文
     */
    @Override
    public void init() {
        beanNames = new ArrayList<>();
        beanDefinitions = new ConcurrentHashMap<>();
        singletonObjects = new ConcurrentHashMap<>();
        earlySingletonObjects = new ConcurrentHashMap<>();
        singletonFactories = new ConcurrentHashMap<>();
        componentScanner = new ComponentScanner();

        ApplicationContextAwareInitializer.getInstance().setApplicationContext(applicationContext);

        try {
            // 扫描包
            componentScanner.scan(applicationContext);

            for (Class<?> clazz : componentScanner.getComponentScanClasses()) {
                if (clazz.isAnnotation()) {
                    continue;
                }
                BeanDefinition beanDefinition = createBeanDefinition(clazz);
                if (beanDefinition != null) {
                    putBeanDefinition(beanDefinition);
                    List<BeanDefinition> innerBeanDefinitions = BeanInitializeUtils.getBeanDefinitionsFromConfiguration(clazz, beanDefinition.getBeanName());
                    for (BeanDefinition innerBeanDefinition : innerBeanDefinitions) {
                        putBeanDefinition(innerBeanDefinition);
                    }
                }
            }

            // 对BeanDefinition进行排序
            BeanInitializeUtils.sortBeanDefinitions(beanNames, beanDefinitions);

            // 对BeanDefinition进行初始化
            for (String beanName : beanNames) {
                BeanDefinition beanDefinition = beanDefinitions.get(beanName);
                if (beanDefinition.getScope().equals(BeanDefinition.SCOPE_SINGLETON) && !beanDefinition.isLazyInit() && !beanDefinition.isAfterInit()) {
                    createSingletonBean(beanDefinition);
                }
            }
        } catch (Exception e) {
            throw new BeansException(e);
        }
    }

    /**
     * 在初始化之后执行的操作
     */
    @Override
    public void afterInit() {
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = beanDefinitions.get(beanName);
            if (beanDefinition.getScope().equals(BeanDefinition.SCOPE_SINGLETON) && !beanDefinition.isLazyInit() && beanDefinition.isAfterInit()) {
                createSingletonBean(beanDefinition);
            }
        }
    }

    /**
     * 获取所有的Bean定义
     *
     * @return Bean定义数组
     */
    @Override
    public BeanDefinition[] getBeanDefinitions() {
        return beanDefinitions.values().toArray(new BeanDefinition[0]);
    }

    /**
     * 销毁Bean上下文
     */
    @Override
    public void destroy() {
        if (beanNames != null) {
            beanNames.clear();
            beanNames = null;
        }
        if (singletonObjects != null) {
            singletonObjects.clear();
            singletonObjects = null;
        }
        if (earlySingletonObjects != null) {
            earlySingletonObjects.clear();
            earlySingletonObjects = null;
        }
        if (singletonFactories != null) {
            singletonFactories.clear();
            singletonFactories = null;
        }
        if (beanDefinitions != null) {
            beanDefinitions.clear();
            beanDefinitions = null;
        }
        if (componentScanner != null) {
            componentScanner.destroy();
            componentScanner = null;
        }
        applicationContext = null;
    }
}
